module objects.beziermesh;

import material.material;
import std.stream;
import std.math;
debug import std.stdio;
import utils.vector2;
import utils.vector3;
import objects.bezierpatch;
import objects.abstractobject;
import objects.triangle;

class Bezier4x4Mesh: AbstractObject {
    private {
        Bezier4x4Patch[] mPatches;
        Triangle[] mTris;
        TriangleNorms[] mNorms;
        int mNumSubdivs;
        Material mMaterial;
    }

    this(Stream rawFile) {
        mNumSubdivs = 2;
        //4x4 vertices (XYZ) pro patch
        int numBezier4x4Patches = rawFile.size / (Bezier4x4Patch.sizeof);

        mPatches = new Bezier4x4Patch[numBezier4x4Patches];
        rawFile.readBlock(mPatches.ptr,
            numBezier4x4Patches*Bezier4x4Patch.sizeof);
    }

    public int patchCount() {
        return mPatches.length;
    }

    //triangulate the loaded bezier patches, using numDivs subdivisions
    //will create numDivs*numDivs*2 triangles per patch
    //may be called multiple times after loading patches from file
    //
    //creates numDivs stripes per patch, each 2*numDivs triangles
    private void subdivide(int numDivs) {
        Vector3f[4] temp;
        //last row of points calculated
        Vector3f[] last = new Vector3f[numDivs+1];
        //normals for last row of points
        Vector3f[] lastnorm = new Vector3f[numDivs+1];
        //buffer for new triangle (circular buffer)
        Vector3f[3] triTmp;
        //buffer for new triangle's normals
        Vector3f[3] normTmp;
        int iTriTmp = 0;

        //result
        Triangle[] tris = new Triangle[numDivs*numDivs*2*mPatches.length];
        TriangleNorms[] normals = new TriangleNorms[numDivs*numDivs*2*mPatches.length];

        //triangulate every patch
        for (int i = 0; i < mPatches.length; i++) {

            //calculate first row of points for current patch
            //to fill last[] and lastnorm[]
            temp[0] = mPatches[i].vertices[0][3];
            temp[1] = mPatches[i].vertices[1][3];
            temp[2] = mPatches[i].vertices[2][3];
            temp[3] = mPatches[i].vertices[3][3];

            for (int v = 0; v <= numDivs; v++) {
                last[v] = bernstein(cast(float)v/numDivs,temp);
                lastnorm[v] = mPatches[i].normal(0,cast(float)v/numDivs);
            }

            debug writefln("Bezier patch %d",i);

            //offset into result arrays for current patch
            int offs = numDivs*numDivs*2*i;

            for (int u = 1; u <= numDivs; u++) {
                temp[0] = bernstein(cast(float)u/numDivs, mPatches[i].vertices[0]);
                temp[1] = bernstein(cast(float)u/numDivs, mPatches[i].vertices[1]);
                temp[2] = bernstein(cast(float)u/numDivs, mPatches[i].vertices[2]);
                temp[3] = bernstein(cast(float)u/numDivs, mPatches[i].vertices[3]);

                for (int v = 0; v <= numDivs; v++) {

                    //create triangle from newly calculated point
                    //t = 0 ... using point from last calculation
                    //t = 1 ... using new point
                    void point(int t) {
                        triTmp[iTriTmp] = last[v];
                        normTmp[iTriTmp] = lastnorm[v];
                        iTriTmp = (iTriTmp+1) % 3;
                        if (v>0) {
                            tris[offs+numDivs*2*(u-1)+(v-1)*2+t].v[0] = 100.0f*triTmp[0];
                            tris[offs+numDivs*2*(u-1)+(v-1)*2+t].v[1+t] = 100.0f*triTmp[1];
                            tris[offs+numDivs*2*(u-1)+(v-1)*2+t].v[2-t] = 100.0f*triTmp[2];

                            //if any calculated normal for the new triangle is
                            //invalid, use triangle normals for all 3 poins
                            if (isnan(normTmp[0].quad_length) || isnan(normTmp[1].quad_length) || isnan(normTmp[2].quad_length)) {
                                normals[offs+numDivs*2*(u-1)+(v-1)*2+t].n[0] =
                                    normals[offs+numDivs*2*(u-1)+(v-1)*2+t].n[1] =
                                    normals[offs+numDivs*2*(u-1)+(v-1)*2+t].n[2] =
                                    tris[offs+numDivs*2*(u-1)+(v-1)*2+t].normal();
                            } else {
                                normals[offs+numDivs*2*(u-1)+(v-1)*2+t].n[0] = normTmp[0];
                                normals[offs+numDivs*2*(u-1)+(v-1)*2+t].n[1+t] = normTmp[1];
                                normals[offs+numDivs*2*(u-1)+(v-1)*2+t].n[2-t] = normTmp[2];
                            }

                            debug writefln("%s",normals[offs+numDivs*2*(u-1)+(v-1)*2]);
                        }
                    }

                    point(0);

                    last[v] = bernstein(cast(float)v/numDivs,temp);
                    lastnorm[v] = mPatches[i].normal(cast(float)u/numDivs,cast(float)v/numDivs);

                    point(1);
                }
            }
        }

        mTris = tris;
        mNorms = normals;
    }

    public Triangle[] getTriangles() {
        subdivide(mNumSubdivs);
        return mTris;
    }

    public TriangleNorms getTriNormals(int triIdx, bool interpolate) {
        if (interpolate) {
            return mNorms[triIdx];
        } else {
            Vector3f n = mTris[triIdx].normal();
            return TriangleNorms(n,n,n);
        }
    }

    public Vector2f[] getTexCoords(int triIdx) {
        //no textures for bezier meshes, sorry
        return [Vector2f(0.0f,0.0f),Vector2f(0.0f,0.0f),Vector2f(0.0f,0.0f)];
    }

    public int numSubdivs() {
        return mNumSubdivs;
    }
    public void numSubdivs(int val) {
        mNumSubdivs = val;
    }

    public Material getTriMaterial(int triIdx) {
        return mMaterial;
    }

    //set material with index <idx> to <mat>
    public void setMaterial(Material mat, int idx = 0) {
        mMaterial = mat;
    }

    //get material <idx>
    public Material getMaterial(int idx = 0) {
        return mMaterial;
    }
}
